﻿using System;
using System.Collections.ObjectModel;
using System.Management.Automation.Provider;
using System.Management.Automation;
using System.Text.RegularExpressions;
using System.Web;
using System.Management.Automation.Runspaces;
using Sitecore.Collections;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Globalization;

using Version = Sitecore.Data.Version;

//using System.Linq;


namespace Cognifide.PowerShell.Shell.Provider
{
    [CmdletProvider("PsSitecoreItemProvider", ProviderCapabilities.None | ProviderCapabilities.Filter)]
    public class PsSitecoreItemProvider : NavigationCmdletProvider, IPropertyCmdletProvider
    {
        private ProviderInfo providerInfo = null;

        public Item GetItemForPath(string path)
        {
            string relativePath = path.Substring(path.IndexOf(':') + 1).Replace('\\','/');
            string databaseName = path.IndexOf(':') < 0 ? PSDriveInfo.Name : path.Substring(0, path.IndexOf(':'));
            Item currentItem = PathUtilities.GetItem(databaseName, relativePath);
            return currentItem;
        }

        protected override bool IsValidPath(string path)
        {
            return GetItemForPath(path) != null;
        }

        protected override ProviderInfo Start(ProviderInfo providerInfo)
        {
            providerInfo.Description = "Sitcore Content Provider";
            this.providerInfo = providerInfo;
            return providerInfo;
        }


        protected override bool ItemExists(string path)
        {
            return GetItemForPath(path) != null;
        }

        protected override bool IsItemContainer(string path)
        {
            return true;
/*
            Item item = GetItemForPath(path);
            return  item != null && item.HasChildren;
*/
        }

        protected override bool ConvertPath(string path, string filter, ref string updatedPath, ref string updatedFilter)
        {
            if (!String.IsNullOrEmpty(filter) || path.Contains('\\'.ToString()) || (path.Contains('/'.ToString()) || path.Contains("`")))
                return false;
            updatedPath = path;
            updatedFilter = Regex.Replace(path, "\\[.*?\\]", "?");
            return true;
        }

        /// <summary>
        /// Moves page to recycle bin on remove-item
        /// </summary>
        /// <param name="path"></param>
        /// <param name="recurse"></param>
        protected override void RemoveItem(string path, bool recurse)
        {
            Item item = GetItemForPath(path);
            if (item != null)
            {
                item.Delete();
            }
        }

        protected override bool HasChildItems(string path)
        {
            path = path.Replace("\\", "/");
            Item item = GetItemForPath(path);
            if (item != null)
            {
                
                return item.HasChildren;
            }
            return false;
        }

        protected override void GetChildItems(string path, bool recurse)
        {

            WildcardPattern wildcard = null;
            if (this.Filter != null && this.Filter.Length > 0)
            {
                wildcard = new WildcardPattern(this.Filter, WildcardOptions.IgnoreCase | WildcardOptions.Compiled);
            }
            Language language;
            Version version;
            GetVersionAndLanguageParams(out version, out language);
            GetChildItemsHelper(path, recurse, wildcard, language, version);

        }

        protected void GetChildItemsHelper(string path, bool recurse, WildcardPattern wildcard, Language language, Version version)

        {
            path = path.Replace("\\", "/");
            if (path.Contains("../"))
            {
                path = path.Substring(path.LastIndexOf("../") + 2);
            }
            Item pageRef = GetItemForPath(path);
            if (pageRef != null)
            {
                ChildList children = pageRef.GetChildren();
                foreach (Item childItem in children)
                {
                    // if language is forced and different from current - retrieve item in the forced language
                    var child = childItem;
                    
                    if (language != null && child.Language.Name != language.Name)
                    {
                        // if item exists in the language - replace it
                        if (ItemManager.GetContentLanguages(child).Contains(language.Name))
                        {
                            child = childItem.Database.GetItem(childItem.ID, language, version);
                        }
                    }
                    else if(version.Number != Sitecore.Data.Version.Latest.Number)
                    {
                        child = childItem.Database.GetItem(childItem.ID, Language.Current, version);
                    }

                    string childUrl = VirtualPathUtility.AppendTrailingSlash(path) + child.Name;

                    // add the properties defined by the page type
                    PSObject psobj = ItemShellExtensions.GetPSObject(this, child);

                    if (wildcard == null || wildcard.IsMatch(child.Name))
                    {
                        if (language == null || language.Name == child.Language.Name)
                        {
                            WriteItemObject(psobj, child.Database + ":" + childUrl, true);
                        }
                    }

                    // if the specified item exists and recurse has been set then 
                    // all child items within it have to be obtained as well
                    if (ItemExists(path) && recurse)
                    {
                        GetChildItemsHelper(childUrl, true, wildcard, language, version);
                    }
                } // foreach (DatabaseTableInfo...
            } // if (PathIsDrive...
            
        }

        private void GetVersionAndLanguageParams(out Version version, out Language language)
        {
            // language selection
            RuntimeDefinedParameterDictionary dic = DynamicParameters as RuntimeDefinedParameterDictionary;
            language = Language.Current;
            if (dic != null && dic["Language"].IsSet)
            {
                var forcedLanguage = dic["Language"].Value.ToString();
                language = LanguageManager.GetLanguage(forcedLanguage);
            }

            version = Sitecore.Data.Version.Latest;
            if (dic != null && dic["Version"].IsSet)
            {
                int forcedVersion = 0;
                if (Int32.TryParse(dic["Version"].Value.ToString(), out forcedVersion))
                {
                    version = new Sitecore.Data.Version(forcedVersion);
                }
            }
        }

        protected override string GetChildName(string path)
        {
            return base.GetChildName(path);
        }

        protected override object GetChildItemsDynamicParameters(string path, bool recurse)
        {
            // We provide an "-unread" parameter so that the user can choose to only list 
            // unread items (and folders and feeds with unread items)

            RuntimeDefinedParameterDictionary dic = DynamicParameters as RuntimeDefinedParameterDictionary;
            var paramAdded = false;

            if (dic == null || !dic.ContainsKey("Language"))
            {

                WriteVerbose("GetChildItemsDynamicParameters:path = " + path);

                ParameterAttribute languageAttrib = new ParameterAttribute();
                languageAttrib.Mandatory = false;
                languageAttrib.ValueFromPipeline = false;

                RuntimeDefinedParameter languageParam = new RuntimeDefinedParameter();
                languageParam.IsSet = false;
                languageParam.Name = "Language";
                languageParam.ParameterType = typeof(string);
                languageParam.Attributes.Add(languageAttrib);

                if(dic == null)
                {
                    dic = new RuntimeDefinedParameterDictionary();
                }
                dic.Add("Language", languageParam);
                paramAdded = true;
            }

            if (!dic.ContainsKey("Version"))
            {
                ParameterAttribute versionAttrib = new ParameterAttribute();
                versionAttrib.Mandatory = false;
                versionAttrib.ValueFromPipeline = false;

                RuntimeDefinedParameter versionParam = new RuntimeDefinedParameter();
                versionParam.IsSet = false;
                versionParam.Name = "Version";
                versionParam.ParameterType = typeof(string);
                versionParam.Attributes.Add(versionAttrib);

                dic.Add("Version", versionParam);
                paramAdded = true;
            }

            return paramAdded? dic : base.GetChildItemsDynamicParameters(path, recurse);
        }

        protected override void GetChildNames(string path, ReturnContainers returnContainers)
        {
            path = path.Replace("\\", "/");
            if (path.Contains("../"))
            {
                path = path.Substring(path.LastIndexOf("../") + 2);
            }
            
            // apply filter
            WildcardPattern wildcard = null;

            RuntimeDefinedParameterDictionary dic = DynamicParameters as RuntimeDefinedParameterDictionary;
            if (this.Filter != null && this.Filter.Length > 0)
            {
                wildcard = new WildcardPattern(this.Filter, WildcardOptions.IgnoreCase | WildcardOptions.Compiled);
            }

            Item pageRef = GetItemForPath(path);
            if (pageRef != null)
            {
                ChildList children = pageRef.GetChildren();
                foreach (Item child in children)
                {
                    if (returnContainers == ReturnContainers.ReturnAllContainers || wildcard == null || wildcard.IsMatch(child.Name))
                    {
                        WriteItemObject(child.Name, path, true);
                    }
                    if (this.Stopping)
                        return;
                    /*
                    if (returnContainers == ReturnContainers.ReturnAllContainers)
                    {
                        GetChildNames(child.Paths.Path, returnContainers);
                    }
*/
                } 
            }
        }

        protected override object GetChildNamesDynamicParameters(string path)
        {
            /* for now we'll just use the same parameters as gci */
            return GetChildItemsDynamicParameters(path, false);
        }

        protected override void GetItem(string path)
        {
            Item item = GetItemForPath(path);

            if (item != null)
            {

                Language language;
                Version version;
                GetVersionAndLanguageParams(out version, out language);


                // if language is forced get the proper version in forced language
                if (language != null && item.Language.Name != language.Name &&
                    ItemManager.GetContentLanguages(item).Contains(language.Name))
                {
                    item = item.Database.GetItem(item.ID, language, version);
                }
                // if language isn't forced but version is
                else if (version.Number != Sitecore.Data.Version.Latest.Number)
                {
                    item = item.Database.GetItem(item.ID, language, version);
                }

                // add the properties defined by the page type
                PSObject psobj = ItemShellExtensions.GetPSObject(this, item);

                WriteItemObject(psobj, path, item.HasChildren);
            }
        }

        protected override object GetItemDynamicParameters(string path)
        {
            return GetChildItemsDynamicParameters(path, false);
        }


        protected override void Stop()
        {
            // perform any cleanup
        }

        protected override Collection<PSDriveInfo> InitializeDefaultDrives()
        {
            Collection<PSDriveInfo> result = new Collection<PSDriveInfo>();

            foreach (Database database in Factory.GetDatabases())
            {
                PSDriveInfo drive = new PSDriveInfo(database.Name,
                                                    providerInfo,
                                                    String.Format("\\sitecore\\",database.Name),
                                                    String.Format("Sitecore database: '{0}'",database.Name),
                                                    PSCredential.Empty);
                result.Add(drive);
            }

            return result;
        }

        #region Implementation of IPropertyCmdletProvider

        public void GetProperty(string path, Collection<string> providerSpecificPickList)
        {
            Item item = GetItemForPath(path);
            if (item != null)
            {
                // create PSObject from the FileSystemInfo instance
                PSObject psobj = PSObject.AsPSObject(item);

                // create the PSObject to copy properties into and that we will return
                PSObject result = new PSObject();

                foreach (string name in providerSpecificPickList)
                {
                    // Copy all the properties from the original object into ’result’
                    PSPropertyInfo prop = psobj.Properties[name];
                    object value = null;
                    if (prop != null)
                    {
                        value = prop.Value;
                    }
                    else
                    {
                        WriteWarning(String.Format("Property name ’{0}’ doesn’t exist for item at path ’{1}’", name,
                                                   path));
                    }
                    result.Properties.Add(new PSNoteProperty(name, value));
                }

                foreach (string name in providerSpecificPickList)
                {
                    Field field = item.Fields[name];
                    if (field != null)
                    {
                        result.Properties[name].Value = field.Value;
                    }
                }

                WritePropertyObject(result, path);
            }
        }

        public object GetPropertyDynamicParameters(string path, Collection<string> providerSpecificPickList)
        {
            return null;
        }

        public void SetProperty(string path, PSObject propertyValue)
        {
            Item item = GetItemForPath(path);
            if (item != null)
            {
                string name = propertyValue.Members["Name"].ToString();
                // create PSObject from the FileSystemInfo instance
                PSObject psobj = PSObject.AsPSObject(item);

                // create the PSObject to copy properties into and that we will return
                PSObject result = new PSObject();

                // Copy all the properties from the original object into ’result’
                PSPropertyInfo prop = psobj.Properties[name];
                object value = null;
                if (prop != null)
                {
                    prop.Value = value;
                }
                else
                {
                    //result.Properties.Add(new PSNoteProperty("", value));
                    Field pageProp = item.Fields[name];
                    value = null;
                    if (pageProp != null)
                    {
                        result.Properties[name].Value = value;
                    }
                    WriteWarning(String.Format("Property name ’{0}’ doesn’t exist for item at path ’{1}’", name,
                                               path));
                }
            }
        }

        public object SetPropertyDynamicParameters(string path, PSObject propertyValue)
        {
            return null;
        }

        public void ClearProperty(string path, Collection<string> propertyToClear)
        {
            throw new NotImplementedException();
        }

        public object ClearPropertyDynamicParameters(string path, Collection<string> propertyToClear)
        {
            throw new NotImplementedException();
        }

        #endregion

        internal static void AppendToRunSpace(RunspaceConfiguration runspaceConfiguration)
        {
            runspaceConfiguration.Providers.Append(new ProviderConfigurationEntry("CmsItemProvider",
                                                                                  typeof(PsSitecoreItemProvider),
                                                                                  String.Empty));
        }

        protected override object CopyItemDynamicParameters(string path, string destination, bool recurse)
        {
            Item sourceItem = GetItemForPath(path);
            Item destinationItem = GetItemForPath(destination);
            if (sourceItem != null)
            {
                ID id = new ID(Guid.NewGuid());
                sourceItem.CopyTo(destinationItem, sourceItem.Name, id, recurse);
            }
            return base.CopyItemDynamicParameters(path, destination, recurse);
        }

        protected override void CopyItem(string path, string destination, bool recurse)
        {
        }

        private string GetParentFromPath(string path)
        {
            path = path.Replace('\\', '/').TrimEnd('/');
            int lastLeafIndex = path.LastIndexOf('/');
            string parentPath = path.Substring(0, lastLeafIndex);
            string childPath = path.Substring(lastLeafIndex + 1);
            return parentPath;
        }

        private string GetLeafFromPath(string path)
        {
            path = path.Replace('\\', '/').TrimEnd('/');
            int lastLeafIndex = path.LastIndexOf('/');
            string childPath = path.Substring(lastLeafIndex + 1);
            return childPath;
        }

        protected override void MoveItem(string path, string destination)
        {
            Item sourceItem = GetItemForPath(path);
            Item destinationItem = GetItemForPath(GetParentFromPath(destination));
            string leafName = GetLeafFromPath(destination);
            if (sourceItem != null)
            {
                ID id = new ID(Guid.NewGuid());
                sourceItem.MoveTo(destinationItem);
                if (sourceItem.Name != leafName)
                {
                    sourceItem.Edit(
                        args =>
                            {
                                sourceItem.Name = leafName;
                            });
                }
            }
        }

        protected override void NewItem(string path, string itemTypeName, object newItemValue)
        {
            Item templateItem = GetItemForPath(itemTypeName);
            Database currentDB = Factory.GetDatabase(PSDriveInfo.Name);
            Item createdItem = currentDB.CreateItemPath(path.Replace("\\","/"),templateItem);
        }
    }
}


